#!/usr/bin/env ruby

require 'SQS'
require 'thread'

module SQSsim

# Classes will go here
	class Receiver
	  attr_accessor :halt_flag
	  attr_reader :name

	  def initialize(queue_url, name, fail_rate=0.0, refuse_rate=0.0)
	    @queue_url = queue_url
	    @name = name
	    @fail_rate = fail_rate
	    @refuse_rate = refuse_rate
	    @sqs = SQS.new
	  end

	  def run(empty_queue_pause=1)
	    puts "#{@name}: Started"
	    @halt_flag = false

	    while not @halt_flag
	      # Receive a single message
	      msgs = @sqs.receive_messages(@queue_url, 1)

	      # If there is no message available pause before retrying
	      if msgs.length == 0
		sleep(empty_queue_pause)
		next # Skip the rest of the loop and retry receive_messages
	      end

	      # Obtain ID and body from the first and only message in the array
	      mid = msgs.first[:id]
	      body = msgs.first[:body]

	      # Notify a block that a message has been received
	      yield(self, mid, body, :received) if block_given?

	      # Calculate random percentage dice rolls used as likelihood of this
	      # receiver failing or refusing to process a message.
	      fail_roll = rand
	      refuse_roll = rand

	      if (refuse_roll < @refuse_rate)
		# Receiver refuses message, its visibility timeout is reset to zero
		@sqs.change_message_visibility(@queue_url, mid, 0)
		yield(self, mid, body, :refused) if block_given?
		next
	      elsif (fail_roll < @fail_rate)
		# Receiver failed, message is not deleted, nor is the timeout reset
		yield(self, mid, body, :failed) if block_given?
	      else
		# Sleep to simulate processing time. Time to delay is read
		# from the message's body content (anything prior to ',')
		processing_time = body.split(',')[0].to_i
		sleep(processing_time)

		# Message processing completed, delete message
		@sqs.delete_message(@queue_url, mid)
		yield(self, mid, body, :deleted) if block_given?
	      end

	    end
	    puts "#{@name}: Halted"
	  end
	end

	class Event
	  attr_reader :receiver, :mid, :body, :type, :time

	  def initialize(receiver, message_id, message_body, type, time=Time.now)
	    @receiver = receiver
	    @mid = message_id
	    @body = message_body
	    @type = type
	    @time = time
	  end
	end


	class Simulator
	  attr_reader :event_history, :threads

	  def initialize(queue_url, log=$stdout)
	    @queue_url = queue_url
	    @log = log
	    @event_history = {}
	  end

	  def start_receivers(batch_name, pool_size, fail_rate=0, refuse_rate=0)
	    raise "Receivers are already running" unless @receivers.nil?

	    @receivers = []
	    @threads = []
	    mutex = Mutex.new

	    pool_size.times do |i|
	      # Construct a new Receiver object
	      @receivers[i] = Receiver.new(@queue_url, "#{batch_name}#{i}",
		                           fail_rate, refuse_rate)

	      # Run the Receiver in a thread
	      @threads[i] = Thread.new do
		@receivers[i].run do |r, msg_id, msg_body, evt_type|
		  e = Event.new(r, msg_id, msg_body, evt_type)

		  mutex.synchronize do
		    unless @quiet_flag
		      @log.write("#{r.name}: #{e.type} @#{e.time}: " +
		                 "#{e.mid} - '#{e.body}\n'")
		    end

		    # Find the array of historic Events for this message ID
		    evt_list = @event_history[e.mid]
		    if evt_list.nil?
		      # This is the first event for the message, create a new array
		      evt_list = [e]
		      @event_history[e.mid] = evt_list
		    else
		      # This message has an event history, add the event to it
		      evt_list << e
		    end

		    unless @quiet_flag
		      @log.write(" - Event count: #{evt_list.length}\n")
		    end

		  end # Mutex#synchronize
		end # Receiver#run
	      end # Thread#new
	    end
	    return @receivers
	  end

	  def stop_receivers
	    raise "Receivers are not running" if @receivers.nil?

	    @receivers.each {|r| r.halt_flag = true}
	    @receivers = nil
	  end

	  def print_history(min_events=3, history=@event_history)
	    # Find any messages in the history with an interesting number of events
	    interesting = history.reject do |mid,events|
	      events.length < min_events
	    end

	    puts "#{interesting.length} messages with #{min_events}+ events:"

	    interesting.each do |mid,events|
		puts "#{mid} - '#{events.first.body}'"

		# Print the event history of this message in order
		events.each do |e|
		  puts "- #{e.receiver.name}: #{e.type} @#{e.time}"
		end
	    end
	    return interesting.length
	  end

	end



end
